package com.googlecode.restitory.gae;

import java.io.CharArrayReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.http.util.EncodingUtils;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.taskqueue.RetryOptions;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.api.taskqueue.TaskOptions.Method;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.googlecode.mycontainer.commons.file.PathUtil;
import com.googlecode.mycontainer.commons.json.JsonHandler;

public class GAERequestUtil {

	public static Key requestToKey(Request req) {
		return KeyFactory.createKey("resource", req.getPath());
	}

	public static void createEntities(Request req, List<Entity> entities) {
		Key key = requestToKey(req);
		Entity entity = new Entity(key);

		setParent(key, entity);
		setParents(key, entity);

		entities.add(entity);
		entities.add(createContentEntity(entity, req));
	}

	private static void setParents(Key key, Entity entity) {
		String path = key.getName();
		List<String> parents = new ArrayList<String>();
		if (path.equals("/")) {
			entity.setProperty("parents", parents);
			return;
		}
		do {
			path = PathUtil.parentPath(path);
			if (path == null) {
				path = "/";
			}
			parents.add(0, path);
		} while (!path.equals("/"));
		entity.setProperty("parents", parents);
	}

	private static void setParent(Key key, Entity entity) {
		String name = key.getName();
		String parentPath = PathUtil.parentPath(name);
		if (parentPath == null) {
			parentPath = "/";
		}
		entity.setProperty("parent", parentPath);
	}

	private static Entity createContentEntity(Entity entity, Request req) {
		String contentType = req.getContentType();
		entity.setProperty("header-Content-Type", contentType);

		Key contentKey = toContentKey(entity.getKey());
		Entity contentEntity = new Entity(contentKey);
		Content content = req.getContent();
		if (content instanceof TextContent) {
			if (contentType == null) {
				contentType = "text/plain";
			}
			contentEntity.setUnindexedProperty("content", new Text(
					((TextContent) content).toString()));
		} else {
			if (contentType == null) {
				contentType = "application/octet-stream";
			}
			contentEntity.setUnindexedProperty("content", new Blob(
					((BinaryContent) content).getValue()));
		}

		if (contentType.startsWith("application/json")) {
			indexJson((TextContent) content, entity);
		}

		return contentEntity;
	}

	public static void indexJson(TextContent content, Entity entity) {
		CharArrayReader reader = new CharArrayReader(content.getValue());
		JsonParser parser = new JsonParser();
		JsonElement parsed = parser.parse(reader);
		if (parsed.isJsonObject()) {
			JsonObject jsonObject = (JsonObject) parsed;
			Set<Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
			for (Entry<String, JsonElement> entry : entrySet) {
				String key = entry.getKey();
				JsonElement value = entry.getValue();
				if (value.isJsonNull() || value.isJsonPrimitive()
						|| value.isJsonArray()) {
					Object v = getJsonValue(value);
					entity.setProperty("attr-" + key, v);
				}
			}
		}
	}

	private static Object getJsonValue(JsonElement value) {
		if (value.isJsonNull()) {
			return null;
		}
		if (value.isJsonPrimitive()) {
			JsonPrimitive primitive = (JsonPrimitive) value;
			if (primitive.isBoolean()) {
				return primitive.getAsBoolean();
			}
			if (primitive.isNumber()) {
				return primitive.getAsNumber();
			}
			if (primitive.isString()) {
				return primitive.getAsString();
			}
		}
		if (value.isJsonArray()) {
			JsonArray array = (JsonArray) value;
			Iterator<JsonElement> it = array.iterator();
			List<Object> ret = new ArrayList<Object>();
			while (it.hasNext()) {
				JsonElement child = it.next();
				if (child.isJsonNull() || child.isJsonPrimitive()) {
					Object v = getJsonValue(child);
					ret.add(v);
				}
			}
			return ret;
		}
		throw new RuntimeException("unsupported: " + value);
	}

	public static Key toContentKey(Key key) {
		return KeyFactory.createKey(key, "content", key.getName());
	}

	public static void populateResponse(Entity entity, Response resp) {
		Set<Entry<String, Object>> props = entity.getProperties().entrySet();
		for (Entry<String, Object> entry : props) {
			String key = entry.getKey();
			Object value = entry.getValue();
			if (key.startsWith("header-")) {
				resp.getHeaders().set(key.substring("header-".length()),
						(value == null ? null : value.toString()));
			}
		}
	}

	public static void populateContentResponse(Entity contentEntity,
			Response resp) {
		Object content = contentEntity.getProperty("content");
		if (content instanceof Blob) {
			resp.setContent(((Blob) content).getBytes());
		} else {
			resp.setContent(((Text) content).getValue());
		}
	}

	public static void writeResponse(Response resp, Object value) {
		String accept = resp.getRequest().getHeaders()
				.getFirst("Accept", "application/json");
		if (accept.equals("application/json")) {
			resp.setContentType(accept).setContent(
					JsonHandler.instance().format(value).toString());
		} else {
			resp.setContentType("text/plain").setContent(
					value == null ? "null" : value.toString());
		}
	}

	public static boolean isBinary(String contentType) {
		if (contentType == null) {
			throw new RuntimeException("contentType is required");
		}

		List<String> texts = new ArrayList<String>();
		texts.add("text/.*");
		texts.add("application/.*json.*");
		texts.add("application/.*javascript.*");

		for (String pattern : texts) {
			if (contentType.matches(pattern)) {
				return false;
			}
		}

		return true;
	}

	public static FilterOperator operatorToFilter(Operator op) {
		switch (op) {
		case EQ:
			return FilterOperator.EQUAL;
		case GT:
			return FilterOperator.GREATER_THAN;
		case GTE:
			return FilterOperator.GREATER_THAN_OR_EQUAL;
		case IN:
			return FilterOperator.IN;
		case LT:
			return FilterOperator.LESS_THAN;
		case LTE:
			return FilterOperator.LESS_THAN_OR_EQUAL;
		case NEQ:
			return FilterOperator.NOT_EQUAL;

		}
		throw new RuntimeException("not supported: " + op);
	}

	public static SortDirection directionToSort(Direction dir) {
		switch (dir) {
		case ASC:
			return SortDirection.ASCENDING;
		case DESC:
			return SortDirection.DESCENDING;
		}
		throw new RuntimeException("not supported: " + dir);
	}

	public static FetchOptions getFetch(Integer offset, Integer limit) {
		FetchOptions ret = FetchOptions.Builder.withDefaults();
		if (offset != null) {
			ret.offset(offset);
		}
		if (limit != null) {
			ret.limit(limit);
		}
		return ret;
	}

	public static TaskOptions requestToTask(Request req) {
		Method method = TaskOptions.Method.valueOf(req.getType().name());
		RetryOptions retry = RetryOptions.Builder.withTaskRetryLimit(1);
		TaskOptions task = TaskOptions.Builder.withUrl(req.getPath())
				.method(method).retryOptions(retry);

		Content content = req.getContent();
		String contentType = req.getContentType();
		if (contentType != null) {
			task.header("Content-Type", contentType);
			if (content instanceof TextContent) {
				byte[] bytes = EncodingUtils.getBytes(content.toString(),
						"utf8");
				task.payload(bytes);
				task.header("X-Payload-Text", "utf8");
			} else if (content instanceof BinaryContent) {
				task.payload(((BinaryContent) content).getValue());
			}
		}

		return task;
	}
}
